package ua.com.softway.date;

import java.io.Serializable;
import java.text.ParseException;
import java.util.Calendar;
import java.util.GregorianCalendar;

/**
 * Non UTC (local) date implementation
 */
public class DateTime implements Serializable, Comparable<DateTime> {
	private int day; // 24
	private int month; // 12
	private int year; // 2007
	private int hour; // 23
	private int minute; // 59
	private int second; // 59
	private int millis; // 999

	public DateTime(int year, int month, int day, int hour, int minute, int second,
			int millis) {
		GregorianCalendar calendar = new GregorianCalendar();
		calendar.clear();

		calendar.set(GregorianCalendar.YEAR, year);
		calendar.set(GregorianCalendar.MONTH, month - 1);
		calendar.set(GregorianCalendar.DAY_OF_MONTH, day);
		calendar.set(GregorianCalendar.HOUR_OF_DAY, hour);
		calendar.set(GregorianCalendar.MINUTE, minute);
		calendar.set(GregorianCalendar.SECOND, second);
		calendar.set(GregorianCalendar.MILLISECOND, millis);

		fromGregorianCalendar(calendar);
	}

	private void fromGregorianCalendar(Calendar calendar) {
		year = calendar.get(GregorianCalendar.YEAR);
		month = calendar.get(GregorianCalendar.MONTH) + 1;
		day = calendar.get(GregorianCalendar.DAY_OF_MONTH);
		hour = calendar.get(GregorianCalendar.HOUR_OF_DAY);
		minute = calendar.get(GregorianCalendar.MINUTE);
		second = calendar.get(GregorianCalendar.SECOND);
		millis = calendar.get(GregorianCalendar.MILLISECOND);
	}

	public DateTime(int year, int month, int day, int hour, int minute, int second) {
		this(year, month, day, hour, minute, second, 0);
	}

	public DateTime(int year, int month, int day, int hour, int minute) {
		this(year, month, day, hour, minute, 0);
	}

	public DateTime(int year, int month, int day) {
		this(year, month, day, 0, 0, 0);
	}

	private DateTime() {
		fromGregorianCalendar(new GregorianCalendar());
	}

	// TODO reimplement
	public long getUTC() {
		return toJavaCalendar().getTimeInMillis();
	}

	public int getMonth() {
		return month;
	}

	public int getYear() {
		return year;
	}

	public int getDay() {
		return day;
	}

	public int getHour() {
		return hour;
	}

	public int getMinute() {
		return minute;
	}

	public int getSecond() {
		return second;
	}

	public int getMillis() {
		return millis;
	}

	public boolean sameDay(DateTime d) {
		return year == d.getYear() && month == d.getMonth()
				&& day == d.getDay();
	}

	public boolean after(DateTime d) {
		return compareTo(d) > 0;
	}

	public boolean before(DateTime d) {
		return compareTo(d) < 0;
	}

	/**
	 * Return difference days
	 */
	public int differenceInDays(DateTime d) {
		return (int) Math.round((getUTC() - d.getUTC()) / 24.0 / 60 / 60 / 1000);
	}

	public DateTime addMillis(int millis) {
	    return new DateTime(year, month, day, hour, minute, second, this.millis + millis);
	}
	
	public DateTime addSeconds(int seconds) {
	    return new DateTime(year, month, day, hour, minute, second + seconds, millis);
	}
	
	public DateTime addMinutes(int minutes) {
	    return new DateTime(year, month, day, hour, minute + minutes, second, millis);
	}
	
    public DateTime addHours(int hours) {
        return new DateTime(year, month, day, hour + hours, minute, second, millis);
    }
    
	public DateTime addDays(int days) {
		return new DateTime(year, month, day + days, hour, minute, second, millis);
	}
	
	public DateTime addMonthes(int monthes) {
	    return new DateTime(year, month + monthes, day, hour, minute, second, millis);
	}

	public int getDayOfWeek() {
		Calendar cal = toJavaCalendar();

		return cal.get(Calendar.DAY_OF_WEEK);
	}
	
	public int getWeekOfYear() {
        Calendar cal = toJavaCalendar();

        return cal.get(Calendar.WEEK_OF_YEAR);
	}

	private Calendar toJavaCalendar() {
        GregorianCalendar calendar = new GregorianCalendar();

        calendar.clear();
        
        calendar.set(GregorianCalendar.YEAR, year);
        calendar.set(GregorianCalendar.MONTH, month - 1);
        calendar.set(GregorianCalendar.DAY_OF_MONTH, day);
        calendar.set(GregorianCalendar.HOUR_OF_DAY, hour);
        calendar.set(GregorianCalendar.MINUTE, minute);
        calendar.set(GregorianCalendar.SECOND, second);
        calendar.set(GregorianCalendar.MILLISECOND, millis);
        
        return calendar;
	}

	public static DateTime now() {
		return new DateTime();
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + day;
		result = prime * result + hour;
		result = prime * result + millis;
		result = prime * result + minute;
		result = prime * result + month;
		result = prime * result + second;
		result = prime * result + year;
		return result;
	}
	
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof DateTime))
			return false;
		final DateTime other = (DateTime) obj;
		if (day != other.day)
			return false;
		if (hour != other.hour)
			return false;
		if (millis != other.millis)
			return false;
		if (minute != other.minute)
			return false;
		if (month != other.month)
			return false;
		if (second != other.second)
			return false;
		if (year != other.year)
			return false;
		return true;
	}

	@Override
	public String toString() {
		return String.format("%04d.%02d.%02d %02d:%02d:%02d.%03d", year, month,
				day, hour, minute, second, millis);
		//        return String.format("%04d.%02d.%02d %02d:%02d:%02d.%03d", getYear(), getMonth(), getDay(), getHour(), getMinute(), getSecond(), getMillis());
	}

	public int compareTo(DateTime o) {
		if (year > o.year)
			return 1;
		if (year < o.year)
			return -1;

		if (month > o.month)
			return 1;
		if (month < o.month)
			return -1;

		if (day > o.day)
			return 1;
		if (day < o.day)
			return -1;

		if (hour > o.hour)
			return 1;
		if (hour < o.hour)
			return -1;

		if (minute > o.minute)
			return 1;
		if (minute < o.minute)
			return -1;

		if (second > o.second)
			return 1;
		if (second < o.second)
			return -1;

		if (millis > o.millis)
			return 1;
		if (millis < o.millis)
			return -1;

		return 0;
	}

	public java.util.Date toJavaDate() {
		return toJavaCalendar().getTime();
	}

	public static DateTime fromJavaDate(java.util.Date date) {
		DateTime d = new DateTime();

		GregorianCalendar calendar = new GregorianCalendar();
		calendar.setTime(date);

		d.fromGregorianCalendar(calendar);

		return d;
	}

	public DateTime yearAfter() {
		return new DateTime(year + 1, month, day, hour, minute, second, millis);
	}

	public DateTime yearAgo() {
		return new DateTime(year - 1, month, day, hour, minute, second, millis);
	}

	public DateTime weekAgo() {
		return new DateTime(year, month, day - 7, hour, minute, second, millis);
	}

	public DateTime weekAfter() {
		return new DateTime(year, month, day + 7, hour, minute, second, millis);
	}

	public DateTime monthAfter() {
		return new DateTime(year, month + 1, day);
	}

	public DateTime yesterday() {
		return new DateTime(year, month, day - 1, hour, minute, second, millis);
	}

	public DateTime tomorrow() {
		return new DateTime(year, month, day + 1, hour, minute, second, millis);
	}

	public DateTime monthAgo() {
		return new DateTime(year, month - 1, day, hour, minute, second, millis);
	}

	public DateTime daysAgo(int i) {
		return new DateTime(year, month, day - i, hour, minute, second, millis);
	}

	public DateTime daysAfter(int i) {
		return new DateTime(year, month, day + i, hour, minute, second, millis);
	}

	public DateTime hourAfter() {
		return new DateTime(year, month, day, hour + 1, minute, second, millis);
	}

	public DateTime hourAgo() {
		return new DateTime(year, month, day, hour - 1, minute, second, millis);
	}

	public Date date() {
		return new Date(year, month, day);
	}
	
	/**
	 * 
	 * @param begin inclusive
	 * @param end inclusive
	 * @return
	 */
	public boolean between(DateTime begin, DateTime end) {
	    if (begin.after(this)) return false;
	    if (end.before(this)) return false;
	    
	    return true;
	}
	
	public DateTime convert(String fromZone, String toZone) {
	    DatePattern convert = new DatePattern("yyyy-MM-dd HH:mm:ss S");
	    
	    String s = convert.format(this, toZone);
	    
	    try {
            return convert.parseDateTime(s, fromZone);
        } catch (ParseException e) {
            throw new RuntimeException(e); // shoud not happen
        }
	}
}
